home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 13316 / 13316.xpi / content / firefoxOverlay.js next >
Encoding:
JavaScript  |  2009-09-10  |  50.0 KB  |  1,400 lines

  1.  
  2. /* Copyright (C) 2009 Norman Solomon
  3.  * e-mail: historytree.addon@yahoo.com
  4.  * 
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the License.
  12.  */
  13.  
  14. // ==============================================================================
  15. // *** NOTES REGARDING INTEGRATION OF THIS OVERLAY WITH THE FIREFOX WINDOW
  16. // ------------------------------------------------------------------------------
  17. // One global variable and its associated class are declared in this overlay.
  18. // All other globals used by History Tree are declared in "historyViewer.js", 
  19. // which is opened via window.open() as a self-contained, non-modal XUL window.
  20. // ==============================================================================
  21.  
  22. // Global variable
  23. var historyTree;
  24.  
  25. // ================================================================
  26. // Extension initialization - Called when Firefox window is opened
  27. // ================================================================
  28. window.addEventListener("load", historyTree_Initialize, false);
  29. function historyTree_Initialize() 
  30. {
  31.     // Create global historyTree object
  32.     historyTree = new HistoryTree();
  33.  
  34.     // Initialize the extension
  35.     historyTree.initialize();
  36. }
  37.  
  38. // ==========================================================
  39. // Extension clean up - Called when Firefox window is closed
  40. // ==========================================================
  41. window.addEventListener("unload", historyTree_CleanUp, false);
  42. function historyTree_CleanUp()
  43. {
  44.     // Destroy all listeners and free up memory used
  45.     historyTree.cleanUp();
  46.  
  47.     // Clear global variable
  48.     historyTree = null;
  49. }
  50.  
  51. // ===========================================================================
  52. // HistoryTree class and its prototype *** SPANS THE REST OF THIS OVERLAY ***
  53. // ===========================================================================
  54. function HistoryTree() {}
  55.  
  56. HistoryTree.prototype = 
  57. {
  58.     // Class wide variables
  59.     cNodeList: new Array(),            // List of HistoryNode objects for all FF Tabs
  60.     cExtHistTreeWin: null,            // "historyViewer.xul" (History Tree window)     
  61.     cTabLoadInterval: null,            // setInterval() while FF Tab content is loading
  62.     cExtLoadProcess: true,            // Used for FF startup and "Stop Private Browsing"
  63.     cHistEvent: "None",                // FF sessionHistory event that occured
  64.     cHistEventTabID: "",            // FF sessionHistory event tabID
  65.     cHistNodeAdded: false,            // Set/used in sessHist "New" and pageLoaded()
  66.     cFFContextMenuURL: "",            // Detects "Open Link in New Tab" for a "# link"
  67.     cOpenRecentlyClosed: false,        // Used to restore "Recently Closed Tabs"
  68.     cRecentlyClosedTabIDs: null,    // Used to restore "Recently Closed Tabs"
  69.     cStartPrivBrow: false,            // Used to clear history if "Start Private Browsing"
  70.  
  71.     // =====================================
  72.     // HistoryTree extension initialization
  73.     // =====================================
  74.     initialize: function()
  75.     {
  76.         // Try to initialize the extension
  77.         try
  78.         {
  79.             // Put FF start date/time in App storage (for display in ext window)
  80.             var startDate = Application.storage.get("FFstartDate", null);
  81.             if (startDate === null)
  82.                 Application.storage.set("FFstartDate", new Date());
  83.             
  84.             // Add page-load, tab and menu click FF event listeners
  85.             historyTree.addOrRemoveEventListeners(true);
  86.  
  87.             // Set class flag that causes function exits while Tabs load
  88.             historyTree.cExtLoadProcess = true;
  89.  
  90.             // Start setInterval() that runs until all Tabs are loaded
  91.             historyTree.cTabLoadInterval = setInterval
  92.                 (historyTree.createSessHistWhenTabsFinishLoading, 800);
  93.         }
  94.         catch (e)
  95.         {
  96.             alert("History Tree Extension - Overlay Initialization Error");
  97.         }
  98.     },
  99.  
  100.     // ================================
  101.     // HistoryTree extension clean-up
  102.     // ================================
  103.     cleanUp: function()
  104.     {
  105.         // Close the "historyViewer.xul" window if shows this FF window history
  106.         // *** NOTE - cExtHistTreeWin may have been set in "historyViewer.js"
  107.         if (historyTree.cExtHistTreeWin)
  108.         {
  109.             if (!historyTree.cExtHistTreeWin.closed)
  110.             {
  111.                 if (historyTree.cExtHistTreeWin.gParentFFWin === window)
  112.                     historyTree.cExtHistTreeWin.close();
  113.             }
  114.         }
  115.  
  116.         // Remove page-load, tab and menu click FF event listeners
  117.         historyTree.addOrRemoveEventListeners(false);
  118.  
  119.         // Remove window load and unload listeners
  120.         window.removeEventListener("load", historyTree_Initialize, false);
  121.         window.removeEventListener("unload", historyTree_CleanUp, false);
  122.     },
  123.  
  124.     // ==========================================================
  125.     // Adds or removes FF event listeners used by the extension
  126.     // ==========================================================
  127.     addOrRemoveEventListeners: function(adding)
  128.     {
  129.         // Page-load and gBrowser sessionHistory listeners
  130.         const NOTIFY_DOC = Components.interfaces.nsIWebProgress.NOTIFY_STATE_DOCUMENT;
  131.         if (adding)
  132.         {
  133.             window.getBrowser().addProgressListener(historyTree.loadListener, NOTIFY_DOC);
  134.             gBrowser.sessionHistory.addSHistoryListener(historyTree.myHist);
  135.         }
  136.         else
  137.         {
  138.             window.getBrowser().removeProgressListener(historyTree.loadListener, NOTIFY_DOC);
  139.             gBrowser.sessionHistory.removeSHistoryListener(historyTree.myHist);
  140.         }
  141.         
  142.         // Tab open, Tab close and tab selected listeners
  143.         var container = gBrowser.tabContainer;
  144.         if (container)
  145.         {
  146.             if (adding)
  147.             {
  148.                 container.addEventListener("TabOpen", historyTree.tabAdded, false);
  149.                 container.addEventListener("TabSelect", historyTree.tabSelected, false);
  150.                 container.addEventListener("TabClose", historyTree.tabClosed, false);
  151.             }
  152.             else
  153.             {
  154.                 container.removeEventListener("TabOpen", historyTree.tabAdded, false);
  155.                 container.removeEventListener("TabSelect", historyTree.tabSelected, false);
  156.                 container.removeEventListener("TabClose", historyTree.tabClosed, false);
  157.             }
  158.         }
  159.  
  160.         // --------------------------------------------------------------------
  161.         // FF window menu-bar "History" > "Recently Closed Tabs" > popupMenu
  162.         var FFHistUndoMenu = document.getElementById("historyUndoPopup");
  163.         if (FFHistUndoMenu)
  164.         {
  165.             if (adding)        
  166.                 FFHistUndoMenu.addEventListener("click", historyTree.getFFHistUndoClick, false);
  167.             else
  168.                 FFHistUndoMenu.removeEventListener("click", historyTree.getFFHistUndoClick, false);
  169.         }
  170.  
  171.         // FF tab-header-bar context menu (appears via mouse right-click)
  172.         // *** NOTE - "anonid"'s are listed in FF 3.5 (3.0.x ?!) "tabBrowser.xml"
  173.         var FFTabContextMenu = document.getAnonymousElementByAttribute
  174.                                (gBrowser, "anonid", "tabContextMenu");
  175.         if (FFTabContextMenu)
  176.         {
  177.             if (adding)        
  178.                 FFTabContextMenu.addEventListener("click", historyTree.getFFTabContextMenuClick, false);
  179.             else
  180.                 FFTabContextMenu.removeEventListener("click", historyTree.getFFTabContextMenuClick, false);
  181.         }
  182.  
  183.         // FF window menu-bar "Tools" menu - Used for "Start/Stop Privare Browsing"
  184.         var FFHistUndoMenu = document.getElementById("menu_ToolsPopup");
  185.         if (FFHistUndoMenu)
  186.         {
  187.             if (adding)        
  188.                 FFHistUndoMenu.addEventListener("click", historyTree.getFFToolsMenuClick, false);
  189.             else
  190.                 FFHistUndoMenu.removeEventListener("click", historyTree.getFFToolsMenuClick, false);
  191.         }        
  192.  
  193.         // FF content-area context menu (appears via mouse right-click)
  194.         var FFcontextMenu = document.getElementById("contentAreaContextMenu");
  195.         if (FFcontextMenu)
  196.         {
  197.             if (adding)        
  198.                 FFcontextMenu.addEventListener("click", historyTree.getFFcontextMenuURL, false);
  199.             else
  200.                 FFcontextMenu.removeEventListener("click", historyTree.getFFcontextMenuURL, false);
  201.         }        
  202.     },
  203.  
  204.     // ==================================================
  205.     // Fires when a new Tab is added to this FF window
  206.     // ==================================================
  207.     tabAdded: function(event)
  208.     {
  209.         // Add tabID for re-opened "Recently Closed Tab" if req
  210.         if (historyTree.cOpenRecentlyClosed)
  211.             historyTree.cRecentlyClosedTabIDs.push(event.target.linkedPanel);
  212.  
  213.         // Add a new sessionHistory Listener to this new Tab
  214.         var browser = event.target.linkedBrowser;
  215.         browser.sessionHistory.addSHistoryListener(historyTree.myHist);
  216.     },
  217.  
  218.     // ========================================
  219.     // Fires when an FF window Tab is selected 
  220.     // ========================================
  221.     tabSelected: function(event)
  222.     {
  223.         // Exit if FF is still loading
  224.         if (historyTree.cExtLoadProcess)
  225.             return;
  226.  
  227.         // Update cNodeList[] entries if selected Tab is not loading
  228.         // *** NOTE - Updating while Tab is loading must be avoided
  229.         // because of multi-part page download sibling addition errors
  230.         var browser = event.target.linkedBrowser;
  231.         var tabID = event.target.linkedPanel;
  232.         if (!browser.webProgress.isLoadingDocument) 
  233.             historyTree.updateSessionHistoryForTab(tabID);
  234.     },
  235.  
  236.     // =======================================
  237.     // Fires when an FF window Tab is closed
  238.     // =======================================
  239.     tabClosed: function(event)
  240.     {
  241.         // Exit if FF is still loading
  242.         if (historyTree.cExtLoadProcess)
  243.             return;
  244.  
  245.         // Update the cNodeList[] sessHist entries for this Tab
  246.         var tabID = event.target.linkedPanel;
  247.         historyTree.updateSessionHistoryForTab(tabID);
  248.  
  249.         // Store sessHist URIs in last cNodeList[] entry for closed Tab
  250.         var browser = event.target.linkedBrowser;
  251.         historyTree.saveClosedTabURIList(browser.sessionHistory, tabID);
  252.  
  253.         // Remove the closed Tab's sessionHistoryListener
  254.         browser.sessionHistory.removeSHistoryListener(historyTree.myHist);
  255.     },
  256.  
  257.     // =======================================================================
  258.     // Fires when user clicks on any FF "Recently Closed Tabs" popupMenu item
  259.     // *** NOTE - popupMenu is populated dynamically, so items have no id's
  260.     // =======================================================================
  261.     getFFHistUndoClick: function(event)
  262.     {
  263.         // *** NOTE - Can get menu item labels for UI spoken lang like this;
  264.         // gNavigatorBundle.getString("menuOpenAllInTabs.label");
  265.  
  266.         // Set class vars used to restore "Recently Closed Tabs"
  267.         historyTree.cOpenRecentlyClosed = true;
  268.         historyTree.cRecentlyClosedTabIDs = new Array();
  269.     },
  270.  
  271.     // ==================================================================
  272.     // Fires when user clicks on any FF tab-header-bar context menu item
  273.     // ==================================================================
  274.     getFFTabContextMenuClick: function(event)
  275.     {
  276.         // Check for "Undo Close Tab" menu-item click
  277.         if (event.target.id === "context_undoCloseTab")
  278.         {
  279.             // Set class vars used to restore "Recently Closed Tabs"
  280.             historyTree.cOpenRecentlyClosed = true;
  281.             historyTree.cRecentlyClosedTabIDs = new Array();
  282.         }
  283.     },
  284.  
  285.     // ===================================================================
  286.     // Fires when user clicks on any FF window menu-bar "Tools" menu item
  287.     // ===================================================================
  288.     getFFToolsMenuClick: function(event)
  289.     {
  290.         // Check for "Start/Stop Private Browsing" menu-item click
  291.         if (event.target.id === "privateBrowsingItem")
  292.         {
  293.             // Set class flag that causes function exits while Tabs load
  294.             historyTree.cExtLoadProcess = true;
  295.  
  296.             // Set class flag if FF is user clicked on "Start Private Browsing"
  297.             var inPrivBrow = Components.classes["@mozilla.org/privatebrowsing;1"].
  298.                     getService(Components.interfaces.nsIPrivateBrowsingService).
  299.                     privateBrowsingEnabled;
  300.             
  301.             if (!inPrivBrow)
  302.                 historyTree.cStartPrivBrow = true;
  303.  
  304.             // Start setInterval() that runs until all Tabs are loaded
  305.             historyTree.cTabLoadInterval = setInterval
  306.                 (historyTree.createSessHistWhenTabsFinishLoading, 800);
  307.         }
  308.     },
  309.  
  310.     // ===================================================================
  311.     // Fires when user clicks on any FF content-area context menu item 
  312.     // ===================================================================
  313.     getFFcontextMenuURL: function(event)
  314.     {
  315.         // If user clicked on FF gContextMenu item "Open Link in New Tab" 
  316.         // store the link URL in class var (for use in sessHistory Listener)
  317.         if (event.target.id === "context-openlinkintab" && gContextMenu.onLink)
  318.             historyTree.cFFContextMenuURL = gContextMenu.linkURL;
  319.         else
  320.             historyTree.cFFContextMenuURL = "";
  321.     },
  322.  
  323.     // =====================================================================
  324.     // A new myHist Listener is added to every Tab opened in this FF window
  325.     // =====================================================================
  326.     myHist:
  327.     {
  328.         OnHistoryGoBack : function(aURI) {
  329.             // Set class vars used in pageLoaded()
  330.             historyTree.cHistEvent = "Back";
  331.             historyTree.cHistEventTabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  332.             return true;
  333.         },
  334.  
  335.         OnHistoryGoForward : function(aURI) {
  336.             // Set class vars used in pageLoaded()
  337.             historyTree.cHistEvent = "Forward";
  338.             historyTree.cHistEventTabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  339.             return true;
  340.         },
  341.      
  342.         OnHistoryGotoIndex : function(aIndex, aURI) {
  343.             // Set class vars used in pageLoaded()
  344.             historyTree.cHistEvent = "Goto";
  345.             historyTree.cHistEventTabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  346.             return true;
  347.         },
  348.  
  349.         OnHistoryReload : function(aURI, aFlags) {
  350.             // Set class vars used in pageLoaded()
  351.             historyTree.cHistEvent = "Reload";
  352.             historyTree.cHistEventTabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  353.             return true;
  354.         },
  355.  
  356.         OnHistoryNewEntry : function(aURI) {                
  357.             // Exit if FF just started up, or if user is opening "Recently Closed Tabs"
  358.             if (historyTree.cExtLoadProcess || historyTree.cOpenRecentlyClosed)
  359.                 return true;
  360.  
  361.             // --------------------------------------------------------------------------
  362.             // The aURI item has NOT been added to FF sessionHistory list at this point,
  363.             // and the variable length, cascading, page-load sequence has NOT started
  364.             // --------------------------------------------------------------------------
  365.             // Synchronise the current cNodeList[] sessHist chain for this Tab
  366.             var tabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  367.             historyTree.synchSessHistChainForTab(tabID);
  368.  
  369.             // -----------------------------------------------------------------------------
  370.             // Add the cNodeList[] item here if function pageLoaded() will not be called.
  371.             // This occurs for a "# link" when the page containing the "# link" is showing, 
  372.             // and the user is NOT doing an "Open Link in New Tab" for that "# link"
  373.             // -----------------------------------------------------------------------------
  374.             var sessHist = gBrowser.sessionHistory;
  375.             if (sessHist.count > 0 && aURI.spec !== historyTree.cFFContextMenuURL)
  376.             {
  377.                 // Reset class var used to detect "Open # Link in New Tab" 
  378.                 historyTree.cFFContextMenuURL = "";
  379.  
  380.                 // Check for a "# link" in-page-jump (FF has already scrolled the page)
  381.                 var pos = aURI.spec.indexOf("#");
  382.                 if (pos !== -1)
  383.                 {
  384.                     // Add cNodeList[] entry if current page is "# link" containing page
  385.                     var thisPath = aURI.spec.substr(0, pos);
  386.                     var prevNdx = sessHist.index;
  387.                     var prevItem = sessHist.getEntryAtIndex(prevNdx, false);
  388.                     var prevPath = prevItem.URI.spec.substr(0, pos);
  389.  
  390.                     if (thisPath === prevPath)
  391.                     {
  392.                         historyTree.addInPageLinkHistoryNode(aURI.spec, prevNdx + 1);
  393.                         return true;
  394.                     }
  395.                 }
  396.             }
  397.  
  398.             // ---------------------------------------------------------------
  399.             // Reset class var used to detect "Open # Link in New Tab" 
  400.             historyTree.cFFContextMenuURL = "";
  401.  
  402.             // Set class vars used in pageLoaded()
  403.             historyTree.cHistNodeAdded = false;
  404.             historyTree.cHistEvent = "New";
  405.             historyTree.cHistEventTabID = tabID;
  406.  
  407.             // The variable length, cascading, page-load sequence begins next
  408.             return true;
  409.         },
  410.         
  411.         OnHistoryPurge : function(aParam) {
  412.             // ****  NOTHING IS DONE FOR THIS EVENT  ****
  413.             historyTree.cHistEvent = "Purge";
  414.             historyTree.cHistEventTabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  415.             return true;
  416.         },
  417.      
  418.         QueryInterface: function(aIID) {
  419.             if (aIID.equals(Components.interfaces.nsISHistoryListener) || 
  420.                 aIID.equals(Components.interfaces.nsISupportsWeakReference) || 
  421.                 aIID.equals(Components.interfaces.nsISupports))
  422.             { 
  423.                 return this; 
  424.             } 
  425.             throw Components.Exception(Components.results.NS_ERROR_NO_INTERFACE); 
  426.         }, 
  427.  
  428.         GetWeakReference: function() { 
  429.             return Components.classes["@mozilla.org/appshell/appShellService; 1"]
  430.                 .createInstance(Components.interfaces.nsIWeakReference);
  431.         }
  432.     },
  433.     
  434.  
  435.     // ========================================================================
  436.     // These events fire when web-page SEGMENTS are loaded into Firefox - BUT
  437.     // there seems to be no way to detect when the FINAL segment will be loaded 
  438.     // ========================================================================
  439.     loadListener: 
  440.     {
  441.         QueryInterface: function(aIID)
  442.         {
  443.             if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
  444.                 aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
  445.                 aIID.equals(Components.interfaces.nsISupports))
  446.                     return this;
  447.                     throw Components.results.NS_NOINTERFACE;
  448.         },
  449.  
  450.         onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
  451.         {
  452.             // If you use myListener for more than one tab/window, use
  453.             // aWebProgress.DOMWindow to obtain the tab/window which triggers the state change
  454.             if (aFlag & Components.interfaces.nsIWebProgressListener.STATE_STOP 
  455.              && aFlag & Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW)
  456.             {
  457.                 // This event repeats for some web-pages - Such as e-mail sites etc
  458.                 // In these cases the last element in the nodeList() array is replaced
  459.                 historyTree.pageLoaded();
  460.             }
  461.             return 0;
  462.         },
  463.  
  464.         onLocationChange: function(aProgress, aRequest, aURI)
  465.         {
  466.             // This fires when the location bar changes; i.e load event confirmed
  467.             return 0;
  468.         },
  469.  
  470.         // For definitions of the remaining functions see XULPlanet.com
  471.         onProgressChange: function() {return 0;},
  472.         onStatusChange: function() {return 0;},
  473.         onSecurityChange: function() {return 0;},
  474.         onLinkIconAvailable: function() {return 0;}
  475.     },
  476.  
  477.  
  478.     // ***********************************************************************
  479.     // *****                                                             *****
  480.     // *****     UTILITY FUNCTIONS THAT BUILD historyTree.cNodeList[]    *****
  481.     // *****     WHICH STORES THE SESSION HISTORY FOR THIS FF WINDOW     *****
  482.     // *****                                                             *****
  483.     // ***********************************************************************
  484.  
  485.     // =============================================================
  486.     // Returns a HistoryNode for storage in historyTree.cNodeList[]
  487.     // =============================================================
  488.     histNode: function(tab, uri)
  489.     {
  490.         // Define the HistoryNode object
  491.         var HistoryNode = function (tab, uri)
  492.         {
  493.             this.tab = tab;        // FF tabID of Tab containing FF sessHist
  494.             this.sessHistNdx;    // FF sessHist.index
  495.             this.uri = uri;        // FF sessHist[index].URI.spec
  496.             this.uCaseUri;        // Used to speed up search
  497.             this.desc;            // FF sessHist[index].title (web-page desc)
  498.             this.uCaseDesc;        // Used to speed up ext win searches
  499.             this.timeInfo;        // Time web-page was first loaded into tab
  500.  
  501.             this.imgData;        // Data version of web-page ".jpeg" image
  502.             this.imgToDraw;        // ".jpeg" drawn in extension main window
  503.             this.imgCreated;    // Set = true when ".jpeg" is drawn
  504.             this.treeNodeNdx;    // Only used in extension main window
  505.  
  506.             // Array property - Used when restoring "Recently Closed Tabs"
  507.             this.closedTabURIs = null;
  508.         };
  509.  
  510.         // Create and return a HistoryNode object
  511.         var node = new HistoryNode(tab, uri);
  512.         return node;
  513.     },
  514.  
  515.     // =============================================================
  516.     // Opens extension main window - Which shows the sessionHistory
  517.     // Called from two oncommand() events in "firefoxOverlay.xul"
  518.     // =============================================================
  519.     showSessionHistoryExtensionWindow: function() 
  520.     {
  521.         // Close the extension window if its open already
  522.         if (historyTree.cExtHistTreeWin)
  523.         {
  524.             if (!historyTree.cExtHistTreeWin.closed)
  525.                 historyTree.cExtHistTreeWin.close();
  526.         }
  527.         
  528.         // Open or re-open window - which refreshes its data 
  529.         var winPath = "chrome://historyTree/content/historyViewer.xul";
  530.         var chromeFeatures = "chrome,centerscreen,resizable=yes";
  531.         historyTree.cExtHistTreeWin = window.open(winPath,"historyViewer",chromeFeatures);
  532.     },
  533.  
  534.     // ====================================================================
  535.     // Returns dataURL for the web-page image in the Tab with passed tabID
  536.     // ====================================================================
  537.     getWebPageImageData: function(tabID) 
  538.     {
  539.         try
  540.         {
  541.             var browser;    // FF Tab/Browser
  542.             var pageWin;    // FF contentWindow
  543.             var ndx, w, h;    // Integers
  544.             var canvas;        // <canvas...>
  545.             var ctx;        // <canvas...> graphics context
  546.             
  547.             // -------------------------------------------------
  548.             // Get tab/browser for passed tabID
  549.             browser = null;
  550.             ndx = 0;
  551.             while (browser === null && ndx < gBrowser.browsers.length)
  552.             {
  553.                 if (tabID === gBrowser.mTabs[ndx].linkedPanel)
  554.                     browser = gBrowser.getBrowserAtIndex(ndx);
  555.                 else
  556.                     ndx ++;
  557.             }
  558.  
  559.             // Get width and height of tab/browser contentWindow
  560.             pageWin = browser.contentWindow;
  561.             w = pageWin.innerWidth + pageWin.scrollMaxX;
  562.             if (pageWin.scrollMaxY > 0) // Vertical scrollbars present
  563.                 w -= 16;
  564.             
  565.             // h/w ratio MUST be 0.65 or displayed ".jpeg"'s will be distorted
  566.             h = w * 0.65;
  567.             if (pageWin.scrollMaxX > 0) // Horizontal scrollbars present
  568.                 h -= 16;
  569.  
  570.             // Set width and height of canvas that obtains web-page screenshot
  571.             // This canvas is hidden in the right hand side of the FF statusbar
  572.             canvas = document.getElementById("testCanvas");
  573.             canvas.style.width = w + "px";  
  574.             canvas.style.height = h + "px";  
  575.             canvas.width = w;  
  576.             canvas.height = h;  
  577.             
  578.             // Draw web-page screenshot on canvas using a white background
  579.             // Note that (x,y) can be used to get HIDDEN part of a page
  580.             ctx = canvas.getContext('2d');
  581.             ctx.clearRect(0, 0, w, h);
  582.             ctx.drawWindow(pageWin, pageWin.scrollX, pageWin.scrollY, w, h, "white");  
  583.             
  584.             // Return a data string representation of the contentWindow
  585.             // "png" images are SLIGHTLY less blurry, but need more memory
  586.             return canvas.toDataURL("image/jpeg", "");
  587.         }
  588.         catch (e)
  589.         {
  590.             // A blank rectangle will be drawn in extension window
  591.             return null;
  592.         }
  593.     },
  594.  
  595.     // ==============================================================================
  596.     // Adds to, or edits, the HistoryNode's in cNodeList[], depending on FF events
  597.     // ------------------------------------------------------------------------------
  598.     // ONLY called from nsiWebProgressListener, when page-segment load event fires
  599.     // HOWEVER, there is no way to detect when the FINAL page-segment will be loaded 
  600.     // ==============================================================================
  601.     pageLoaded: function()
  602.     {
  603.         var tabID;        // FF Tab identifier
  604.         var sessHist;    // FF sessHist
  605.         var node;        // HistoryNode
  606.  
  607.         // ----------------------------------------------------------
  608.         // Exit if FF Tab page load setInterval() is still running
  609.         if (historyTree.cExtLoadProcess)
  610.         {
  611.             return;
  612.         }
  613.         else if (historyTree.cOpenRecentlyClosed)
  614.         {
  615.             // User clicked on a "Recently Closed Tabs" FF sub-menu item
  616.             historyTree.restoreRecentlyClosedTabs(historyTree.cRecentlyClosedTabIDs);
  617.  
  618.             // Reset class flags
  619.             historyTree.cOpenRecentlyClosed = false;
  620.             historyTree.cRecentlyClosedTabIDs = null;
  621.         }
  622.         else
  623.         {
  624.             // User is opening a new page or navigating to an open page via FF sessHist
  625.             tabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  626.  
  627.             // Ensure user is still on the Tab that initiated the sessHist Listener event
  628.             if (tabID === historyTree.cHistEventTabID)
  629.             {
  630.                 // Add to or adjust cNodeList[] depending on FF sessHist event
  631.                 if (historyTree.cHistEvent === "New" && !historyTree.cHistNodeAdded)
  632.                 {
  633.                     // Add a HistoryNode to the end of cNodeList[]
  634.                     historyTree.updateSessionHistoryForTab(tabID);
  635.                     historyTree.cHistNodeAdded = true;
  636.                 }
  637.                 else
  638.                 {
  639.                     // Update cNodeList[] entry props to match sessHist item
  640.                     sessHist = gBrowser.sessionHistory;
  641.                     node = historyTree.nodeFromSessHistNdx(sessHist.index, tabID);
  642.                     if (node !== null)
  643.                         historyTree.makeHistoryNodeMatchFFsessHist(tabID, node, sessHist);
  644.                 }
  645.             }
  646.         }
  647.     },
  648.  
  649.     // ===================================================
  650.     // Fills cNodeList[] when all FF Tabs finish loading
  651.     // Called via setInterval() when FF window opened
  652.     // ===================================================
  653.     createSessHistWhenTabsFinishLoading: function() 
  654.     {
  655.         // Check if all FF Tabs have finished loading
  656.         if (historyTree.allTabsLoaded())
  657.         {
  658.             // Stop setInterval() and set class flag
  659.             clearInterval(historyTree.cTabLoadInterval);
  660.             historyTree.cTabLoadInterval = null;
  661.             
  662.             // Close extension win if "Private Browsing" started
  663.             if (historyTree.cStartPrivBrow)
  664.             {
  665.                 // Reset class flag
  666.                 historyTree.cStartPrivBrow = false;
  667.  
  668.                 // Find out if FF is in "Private Browsing" mode
  669.                 var inPrivBrow = Components.classes["@mozilla.org/privatebrowsing;1"].
  670.                         getService(Components.interfaces.nsIPrivateBrowsingService).
  671.                         privateBrowsingEnabled;
  672.  
  673.                 if (!inPrivBrow)
  674.                 {
  675.                     // Private browsing was cancelled so do nothing
  676.                     historyTree.cExtLoadProcess = false;
  677.                     return;
  678.                 }
  679.                 else if (historyTree.cExtHistTreeWin && !historyTree.cExtHistTreeWin.closed)
  680.                 {
  681.                     // Close extension window at start of private browsing
  682.                     historyTree.cExtHistTreeWin.close();
  683.                 }
  684.             }
  685.  
  686.             // Clear out class array
  687.             historyTree.cNodeList = new Array();
  688.             
  689.             // Fill cNodeList[] and add sessHist Listeners
  690.             var tabID;
  691.             var brow;
  692.  
  693.             for (var i = 0; i < gBrowser.browsers.length; i++)
  694.             {
  695.                 // Add cNodeList[] sessHist entries for this Tab
  696.                 tabID = gBrowser.mTabs[i].linkedPanel;
  697.                 historyTree.synchSessHistChainForTab(tabID);
  698.  
  699.                 // Add Tab hist Listener (addition via tabAdded() sometimes fails)
  700.                 brow = gBrowser.getBrowserAtIndex(i);
  701.                 brow.sessionHistory.addSHistoryListener(historyTree.myHist);
  702.             }
  703.  
  704.             // Reset class flag
  705.             historyTree.cExtLoadProcess = false;
  706.         }
  707.     },
  708.  
  709.     // ===============================================
  710.     // Adds a HistoryNode to the end of cNodeList[] 
  711.     // ===============================================
  712.     addHistoryNode: function(tabID, sessHist, sessHistNdx) 
  713.     {
  714.         var histItem;    // FF sessHist item
  715.         var histURI;    // FF sessHist item URI
  716.         var node;        // HistoryNode
  717.         var pageDesc;    // String
  718.         var timeNow;    // JS date()
  719.  
  720.         // ------------------------------------------------------
  721.         // Get information describing matching FF sessHist entry
  722.         histItem = sessHist.getEntryAtIndex(sessHistNdx, false);
  723.         histURI = histItem.URI.spec;
  724.  
  725.         // Create a HistoryNode, passing in .tab and .uri
  726.         node = historyTree.histNode(tabID, histURI);
  727.  
  728.         // Set node FF sessHist.index and image first-time draw flag
  729.         node.sessHistNdx = sessHistNdx;
  730.         node.imgCreated = false;
  731.  
  732.         // Set node web-page description, including any "#" suffix
  733.         pageDesc = historyTree.pageDescFor(histItem.title, histURI);
  734.         node.desc = pageDesc;
  735.         
  736.         // Set upper case URI and desc - Used to speed up ext win search
  737.         node.uCaseUri = histURI.toUpperCase();
  738.         node.uCaseDesc = pageDesc.toUpperCase();
  739.         
  740.         // Set time page loaded - Shown as "16.42" etc. in ext window
  741.         timeNow = new Date().toTimeString();
  742.         node.timeInfo = timeNow.substr(0,5);
  743.  
  744.         // Get and set web-page image if page currently visible in Tab
  745.         if (sessHistNdx === sessHist.index)
  746.         {
  747.             // Get node web-page image dataURL for later ".jpeg" display
  748.             node.imgData = historyTree.getWebPageImageData(tabID);
  749.         }
  750.         else
  751.         {
  752.             // Web-page image will be drawn as a white rectangle
  753.             node.imgData = null;
  754.         }
  755.  
  756.         // Add the HistoryNode to the end of cNodeList[]
  757.         historyTree.cNodeList.push(node);
  758.     },
  759.  
  760.     // =====================================================================
  761.     // Adds a "# link" HistoryNode to cNodeList[] - ONLY Called from myHist
  762.     // "New" event. *** NOTE - Will add an out of sequence cNodeList[] entry
  763.     // if user clicks on the "# link" BEFORE the containing page has loaded. 
  764.     // This FF asynch error is fixed in function synchSessHistChainForTab()
  765.     // =====================================================================
  766.     addInPageLinkHistoryNode: function(URI, sessHistNdx) 
  767.     {
  768.         // Add a "# link" HistoryNode to the end of cNodeList[]
  769.         var tabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  770.         var node = historyTree.histNode(tabID, URI);
  771.         
  772.         // Set HistoryNode properties
  773.         node.uCaseUri = URI.toUpperCase();
  774.         node.sessHistNdx = sessHistNdx;
  775.         var timeNow = new Date().toTimeString();
  776.         node.timeInfo = timeNow.substr(0,5);
  777.  
  778.         var pageDesc = historyTree.pageDescFor(gBrowser.contentTitle, URI);
  779.         node.desc = pageDesc;
  780.         node.uCaseDesc = pageDesc.toUpperCase();
  781.         node.imgData = historyTree.getWebPageImageData(tabID);
  782.         node.imgCreated = false; 
  783.         
  784.         // Add HistoryNode to end of cNodeList[]
  785.         historyTree.cNodeList.push(node);
  786.     },
  787.  
  788.     // ======================================================
  789.     // Updates cNodeList[] entry to match FF sessHist item
  790.     // ======================================================
  791.     makeHistoryNodeMatchFFsessHist: function(tabID, node, sessHist)
  792.     {
  793.         // Update page image if page is loaded/visible in passed tabID
  794.         if (node.sessHistNdx === sessHist.index)
  795.         {
  796.             // Set dataURL and flag for ".jpeg" image display
  797.             node.imgData = historyTree.getWebPageImageData(tabID);
  798.             node.imgCreated = false;
  799.         }
  800.  
  801.         // Update cNodeList[] HistoryNode URI
  802.         var histItem = sessHist.getEntryAtIndex(sessHist.index, false);
  803.         var uriSpec = histItem.URI.spec;
  804.         node.uri = uriSpec;
  805.         node.uCaseUri = uriSpec.toUpperCase();
  806.  
  807.         // Update cNodeList[] HistoryNode description
  808.         var pageDesc = historyTree.pageDescFor(histItem.title, node.uri);
  809.         node.desc = pageDesc;
  810.         node.uCaseDesc = pageDesc.toUpperCase();
  811.     },
  812.  
  813.     // ===============================================================
  814.     // Updates current cNodeList[] entry for Tab with passed tabID
  815.     // Called when user clicks on a Tab or opens extension window etc
  816.     // ===============================================================
  817.     updateSessionHistoryForTab: function(tabID)
  818.     {
  819.         var sessHist;                 // FF sessHist
  820.         var histItem;                 // FF sessHist item
  821.         var lastHistNdx, myLastNdx;     // Integers
  822.         var node;                     // HistoryNode
  823.         var newEntry = "";             // String
  824.  
  825.         // ----------------------------------------------------------
  826.         // Only process FF sessionHistory's that have some entries
  827.         sessHist = historyTree.sessHistFromTabID(tabID);
  828.         if (sessHist !== null)
  829.         {
  830.             // *** NOTE - sessHist.count will === 0 if Tab has no content
  831.             // FF 3.0.x menu option "File > New Tab" creates this situation
  832.             // If a Tab does have only one blank page then FF 3.0.x 
  833.             // creates a URI === "about:blank", but FF 3.5.x does not?!
  834.             if (sessHist.count > 0)
  835.             {
  836.                 // Decide whether to add to or update cNodeList[]
  837.                 myLastNdx = historyTree.sessHistLengthForTab(tabID);
  838.                 lastHistNdx = sessHist.count - 1;
  839.                 histItem = sessHist.getEntryAtIndex(lastHistNdx, false);
  840.                 node = historyTree.nodeFromSessHistNdx(lastHistNdx, tabID);
  841.  
  842.                 // Can't check node.uri if node is null - so set flag
  843.                 // (could use constants, but this is readable and clear)
  844.                 if (lastHistNdx > myLastNdx || node === null)
  845.                     newEntry = "child";
  846.                 else if (node.uri !== histItem.URI.spec || myLastNdx > lastHistNdx)
  847.                     newEntry = "sibling";
  848.                 else
  849.                     newEntry = "none";
  850.  
  851.                 // If adding a tab root sibling delete the entire tab sub-tree first
  852.                 // This is sometimes needed after "Tools > Clear Recent History"
  853.                 // There is no logical alternative, since doing nothing causes errors
  854.                 if (newEntry === "sibling" && lastHistNdx === 0)
  855.                 {
  856.                     historyTree.deleteAllNodesForTab(tabID);
  857.                     historyTree.addHistoryNode(tabID, sessHist, lastHistNdx);
  858.                     return;
  859.                 }
  860.  
  861.                 // Add to or update cNodeList[] sessHist entries for passed tabID
  862.                 if (newEntry === "child" || newEntry === "sibling")
  863.                 {
  864.                     // The last cNodeList[] child/sibling entry is missing
  865.                     historyTree.addHistoryNode(tabID, sessHist, lastHistNdx);
  866.                 }
  867.                 else
  868.                 {
  869.                     // All cNodeList[] entries have been added for this Tab
  870.                     node = historyTree.nodeFromSessHistNdx(sessHist.index, tabID);
  871.                     if (node !== null)
  872.                     {
  873.                         // Update HistoryNode to match FF sessHist entry 
  874.                         historyTree.makeHistoryNodeMatchFFsessHist(tabID, node, sessHist);
  875.                     }
  876.                 }
  877.             }
  878.         }
  879.     },
  880.  
  881.     // ================================================================
  882.     // Updates cNodeList[] sessHist chain for passed tabID. Called;
  883.     // ----------------------------------------------------------------
  884.     // 1) When FF window opened and from sessHist listener "New" event
  885.     // 2) For all FF Tabs, just before opening extension main window
  886.     // ================================================================
  887.     synchSessHistChainForTab: function(tabID)
  888.     {
  889.         var tabNodes = new Array();                // Temp array
  890.         var node, root, thisNode, nextNode;        // HistoryNode's
  891.         var tabLen, lastHistNdx, myLastNdx;        // Integers
  892.         var sessHist;                            // FF sessHist                    
  893.         var histItem;                            // FF sessHist item
  894.         
  895.         // ---------------------------------------------------------
  896.         // Create cNodeList[] HistoryNode ref array for passed tabID
  897.         for (var ndx = 0; ndx < historyTree.cNodeList.length; ndx++)
  898.         {
  899.             node = historyTree.cNodeList[ndx];
  900.             if (node.tab === tabID)
  901.                 tabNodes.push(node);
  902.         }
  903.  
  904.         // Make sure root node for passed tabID has a zero sessHistNdx 
  905.         if (tabNodes.length > 0)
  906.         {
  907.             root = tabNodes[0];
  908.             if (root.sessHistNdx !== 0)
  909.                 root.sessHistNdx = 0;
  910.         }
  911.  
  912.         // Make sure cNodeList[] chains are incrementally sequential
  913.         // i.e. correct any invalid sessHist chain sequences for tabID
  914.         tabLen = tabNodes.length - 1;
  915.         for (var ndx = 0; ndx < tabLen; ndx++)
  916.         {
  917.             // Reset chain sequence if its not incrementally sequential
  918.             thisNode = tabNodes[ndx];
  919.             nextNode = tabNodes[ndx + 1];
  920.             if (nextNode.sessHistNdx > thisNode.sessHistNdx + 1)
  921.                 nextNode.sessHistNdx = thisNode.sessHistNdx + 1;
  922.         }
  923.  
  924.         // Remove all elements from tabID reference array
  925.         tabNodes.splice(0);
  926.  
  927.         // ----------------------------------------------------------------
  928.         // Synchronise chain in cNodeList[] with current FF sessHist chain
  929.         // i.e. correct any asynchronous FF page-load errors for this Tab
  930.         sessHist = historyTree.sessHistFromTabID(tabID);
  931.         if (sessHist !== null)
  932.         {
  933.             if (sessHist.count > 0)
  934.             {
  935.                 // Check if any cNodeList[] entries are missing from end of chain
  936.                 myLastNdx = historyTree.sessHistLengthForTab(tabID);
  937.                 lastHistNdx = sessHist.count - 1;
  938.                 if (lastHistNdx > myLastNdx)
  939.                 {
  940.                     // Add all missing cNodeList[] entries to end of current chain
  941.                     for (var ndx = myLastNdx + 1; ndx < sessHist.count; ndx ++)
  942.                         historyTree.addHistoryNode(tabID, sessHist, ndx);
  943.                 }
  944.                 
  945.                 // Correct any mismatched cNodeList[] HistoryNode's in current chain
  946.                 for (var ndx = 0; ndx < sessHist.count; ndx ++)
  947.                 {
  948.                     node = historyTree.nodeFromSessHistNdx(ndx, tabID);
  949.                     if (node !== null)
  950.                     {
  951.                         histItem = sessHist.getEntryAtIndex(ndx, false);
  952.                         if (node.uri !== histItem.URI.spec)
  953.                         {
  954.                             // Update HistoryNode for current FF sessHist entry 
  955.                             historyTree.makeHistoryNodeMatchFFsessHist(tabID, node, sessHist);
  956.                         }
  957.                     }
  958.                 }
  959.             }
  960.         }
  961.     },
  962.  
  963.     // ==========================================================
  964.     // Stores sessHist URI list in cNodeList[] entry for tabID
  965.     // Used when user restores Tabs via "Recently Closed Tabs"
  966.     // *** ONLY called when closing a Tab via tabClosed()
  967.     // ==========================================================
  968.     saveClosedTabURIList: function(sessHist, tabID)
  969.     {
  970.         var histItem;    // FF sessHist item
  971.         var node;        // HistoryNode
  972.         var lastNdx;    // Integer
  973.         var sessHistURIs = new Array();
  974.  
  975.         // --------------------------------------------
  976.         // Store sessionHistory URI list in an array
  977.         for (var i = 0; i < sessHist.count; i++)
  978.         {
  979.             histItem = sessHist.getEntryAtIndex(i, false);
  980.             sessHistURIs.push(histItem.URI.spec);
  981.         }
  982.         
  983.         // Attach array to LAST cNodeList[] entry for passed tabID
  984.         // (this array prop will be set = null if Tab is restored)
  985.         lastNdx = historyTree.cNodeList.length - 1;
  986.         for (var i = lastNdx; i >= 0; i--)
  987.         {
  988.             node = historyTree.cNodeList[i];
  989.             if (node.tab === tabID)
  990.             {
  991.                 node.closedTabURIs = sessHistURIs;
  992.                 return;
  993.             }
  994.         }
  995.  
  996.         // Remove all elements from temp array
  997.         sessHistURIs.splice(0);
  998.     },
  999.  
  1000.     // =================================================================
  1001.     // Restores "Recently Closed Tabs" when user chooses this FF option
  1002.     // Called from pageLoaded() - Which is final Tab restoration event
  1003.     // =================================================================
  1004.     restoreRecentlyClosedTabs: function(restoredTabIDs) 
  1005.     {
  1006.         var tabID, oldTabID;        // FF Tab identifiers
  1007.         var sessHist;                // FF sessHist
  1008.         var histItem;                // FF sessHist item
  1009.         var histURI, savedURI;        // FF sessHist item URI
  1010.         var node;                    // HistoryNode
  1011.         var matchFound = false;        // Boolean
  1012.         var ndx, row, pos;            // Integers
  1013.  
  1014.         // --------------------------------------------------
  1015.         // Restore all "Recently Closed Tabs" in cNodeList[]
  1016.         for (var i = 0; i < restoredTabIDs.length; i++)
  1017.         {
  1018.             // Delete any nodes user added while FF was doing Tab restoration
  1019.             tabID = restoredTabIDs[i];
  1020.             historyTree.deleteAllNodesForTab(tabID);
  1021.  
  1022.             // Get FF sessionHistory for restored tabID
  1023.             sessHist = historyTree.sessHistFromTabID(tabID);
  1024.  
  1025.             // Only process FF sessHist URI lists that have some entries
  1026.             if (sessHist !== null)
  1027.             {
  1028.                 if (sessHist.count > 0)
  1029.                 {
  1030.                     // Search BACKWARDS for a matching cNodeList[] saved sessHist
  1031.                     // (this will have been saved when the Tab was closed)
  1032.                     matchFound = false;
  1033.                     ndx = historyTree.cNodeList.length - 1;
  1034.                     while (!matchFound && ndx >= 0)
  1035.                     {
  1036.                         // Make sure saved sessHist is not null and same length
  1037.                         node = historyTree.cNodeList[ndx];
  1038.                         ndx --;
  1039.                         if (node.closedTabURIs !== null)
  1040.                         {
  1041.                             if (node.closedTabURIs.length === sessHist.count)
  1042.                             {
  1043.                                 // Check if saved sessHist URI's match FF sessHist URI's
  1044.                                 matchFound = true;
  1045.                                 row = 0;
  1046.                                 while (matchFound && row < sessHist.count)
  1047.                                 {
  1048.                                     // Get pair of URI's for comparison
  1049.                                     histItem = sessHist.getEntryAtIndex(row, false);
  1050.                                     histURI = histItem.URI.spec;
  1051.                                     savedURI = node.closedTabURIs[row];
  1052.  
  1053.                                     // URI lists don't match if this URI pair don't match
  1054.                                     if (histURI !== savedURI)
  1055.                                     {
  1056.                                         matchFound = false;
  1057.  
  1058.                                         // *** FF BUG CODE AROUND *** i.e. FF can restore
  1059.                                         // the wrong last page if that page is a "# link"
  1060.                                         if (row === sessHist.count - 1)
  1061.                                         {
  1062.                                             pos = histURI.indexOf("#");
  1063.                                             if (pos !== -1)
  1064.                                             {
  1065.                                                 if (histURI.substr(0, pos) 
  1066.                                                 === savedURI.substr(0, pos))
  1067.                                                     matchFound = true;
  1068.                                             }
  1069.                                         }
  1070.                                     }
  1071.                                     // Move to next pair of sessHist URI items
  1072.                                     row ++;
  1073.                                 }
  1074.                             }
  1075.                         }
  1076.                     }
  1077.                     
  1078.                     // Restore closed Tab if a matching cNodeList[] sessHist was found
  1079.                     if (matchFound)
  1080.                     {
  1081.                         // Match found - So reset tabID's in matching cNodeList[] entries
  1082.                         node.closedTabURIs = null; // Stops re-use
  1083.                         oldTabID = node.tab;
  1084.                         for (var ndx = 0; ndx < historyTree.cNodeList.length; ndx++)
  1085.                         {
  1086.                             node = historyTree.cNodeList[ndx];
  1087.                             if (node.tab === oldTabID)
  1088.                                 node.tab = tabID;
  1089.                         }
  1090.                     }
  1091.                     else
  1092.                     {
  1093.                         // No match found - Could occur if FF startup data included 
  1094.                         // some "Recently Closed Tabs" from previous "Save and Quit"
  1095.                         // So add a chain of HistoryNode's to cNodeList[] 
  1096.                         for (var ndx = 0; ndx < sessHist.count; ndx++)
  1097.                         {
  1098.                             // Add HistoryNode to the end of cNodeList[]
  1099.                             historyTree.addHistoryNode(tabID, sessHist, ndx);
  1100.                         }
  1101.                     }
  1102.                 }
  1103.             }
  1104.         }
  1105.     },
  1106.  
  1107.     // ==============================================================
  1108.     // Deletes all cNodeList[] HistoryNodes that have passed tabID
  1109.     // Called just before restoring a "Recently Closed Tab" and if 
  1110.     // a tab root sibling is added in updateSessionHistoryForTab()
  1111.     // ==============================================================
  1112.     deleteAllNodesForTab: function(tabID)
  1113.     {
  1114.         var node;
  1115.         var ndx = 0;
  1116.  
  1117.         // Loop down cNodeList and delete all nodes with passed tabID
  1118.         // *** NOTE - cNodeList.length CHANGES when a HistoryNode is spliced
  1119.         while (historyTree.cNodeList.length > 0 && ndx < historyTree.cNodeList.length)
  1120.         {
  1121.             node = historyTree.cNodeList[ndx];        
  1122.             if (node.tab === tabID)
  1123.                 historyTree.cNodeList.splice(ndx, 1);
  1124.             else
  1125.                 ndx ++;
  1126.         }
  1127.     },
  1128.  
  1129.     // ===========================================================
  1130.     // Returns sessHist for tabID, or null if sessHist not found
  1131.     // ===========================================================
  1132.     sessHistFromTabID: function(tabID)
  1133.     {
  1134.         // Return sessionHistory for passed tabID if its found
  1135.         for (var browNdx = 0; browNdx < gBrowser.browsers.length; browNdx++)
  1136.         {
  1137.             if (tabID === gBrowser.mTabs[browNdx].linkedPanel)
  1138.                 return gBrowser.getBrowserAtIndex(browNdx).sessionHistory;
  1139.         }
  1140.  
  1141.         // Tab sessionHistory was not found
  1142.         return null;
  1143.     },
  1144.  
  1145.     // ===============================================================
  1146.     // Returns current length of cNodeList[] sessHist chain for tabID
  1147.     // ===============================================================
  1148.     sessHistLengthForTab: function(tabID)
  1149.     {
  1150.         var node;
  1151.         var lastNdx = historyTree.cNodeList.length - 1;
  1152.  
  1153.         // -------------------------------------------
  1154.         for (var ndx = lastNdx; ndx >= 0; ndx--)
  1155.         {
  1156.             node = historyTree.cNodeList[ndx];
  1157.             if (node.tab === tabID)
  1158.                 return node.sessHistNdx;
  1159.         }
  1160.  
  1161.         // No matching HistoryNode was found
  1162.         return -1;
  1163.     },
  1164.  
  1165.     // =========================================================
  1166.     // Returns matching cNodeList[] entry or null if no match
  1167.     // =========================================================
  1168.     nodeFromSessHistNdx: function(sessHistNdx, tabID)
  1169.     {
  1170.         var node;
  1171.         var lastNdx = historyTree.cNodeList.length - 1;
  1172.  
  1173.         // -------------------------------------------
  1174.         for (var ndx = lastNdx; ndx >= 0; ndx--)
  1175.         {
  1176.             node = historyTree.cNodeList[ndx];
  1177.             if (node.tab === tabID && node.sessHistNdx === sessHistNdx)
  1178.                 return node;
  1179.         }
  1180.  
  1181.         // No matching HistoryNode was found
  1182.         return null;
  1183.     },
  1184.  
  1185.     // =======================================================
  1186.     // Returns web-page description plus any URI "#" suffix
  1187.     // =======================================================
  1188.     pageDescFor: function(desc, URI)
  1189.     {
  1190.         // Add "#" suffix to page description if URL contains "#"
  1191.         var pos = URI.indexOf("#");
  1192.         if (pos !== -1)
  1193.             return desc + " # " + URI.substr(pos + 1);
  1194.         else
  1195.             return desc;
  1196.     },
  1197.  
  1198.     // ***************************************************************
  1199.     // *****                                                     *****
  1200.     // *****       FUNCTIONS CALLED FROM "historyViewer.js"      *****
  1201.     // *****    WHICH IS TOP LEVEL JS FOR THE EXTENSION WINDOW   *****
  1202.     // *****                                                     *****
  1203.     // ***************************************************************
  1204.  
  1205.     // ===============================================================
  1206.     // Open/Goto history page - ONLY called from "historyViewer.js"
  1207.     // *** NOTE - optNum is passed via "openPageDialog.js" onUnload()
  1208.     // ===============================================================
  1209.     openOrGotoHistoryPage: function(tNode, optNum, selTabID) 
  1210.     {
  1211.         var tabNdx, sessHistNdx;  // Integers
  1212.         var sessHist;              // FF sessHist
  1213.  
  1214.         // ---------------------------------------------------
  1215.         // Open/Goto history page, depending on passed optNum
  1216.         if (optNum === 0)
  1217.         {
  1218.             // Get index of Tab containing required page
  1219.             tabNdx = historyTree.tabNdxFromTabID(tNode.histNode.tab);
  1220.             if (tabNdx !== -1)
  1221.             {
  1222.                 // Select the Tab that contains req page
  1223.                 gBrowser.mTabContainer.selectedIndex = tabNdx;
  1224.         
  1225.                 // Goto req history page within Tab
  1226.                 sessHistNdx = tNode.histNode.sessHistNdx;
  1227.                 sessHist = gBrowser.sessionHistory;
  1228.                 if (sessHist.count > sessHistNdx)
  1229.                     gBrowser.gotoIndex(sessHistNdx);
  1230.             }
  1231.         }
  1232.         else if (optNum === 1)
  1233.         {
  1234.             // Add page to current Tab UNLESS it already shows that page
  1235.             openUILink(tNode.histNode.uri);
  1236.         }
  1237.         else if (optNum === 2)
  1238.         {
  1239.             // Add a new Tab with a page with passed URL in it
  1240.             gBrowser.addTab(tNode.histNode.uri);
  1241.  
  1242.             // Select the added tab
  1243.             gBrowser.mTabContainer.selectedIndex = gBrowser.browsers.length - 1;
  1244.         }
  1245.         else if (optNum === 3)
  1246.         {
  1247.             // Get index of Tab in which the page is to be opened
  1248.             tabNdx = historyTree.tabNdxFromTabID(selTabID);
  1249.             if (tabNdx !== -1)
  1250.             {
  1251.                 // Select the Tab to add the page to
  1252.                 gBrowser.mTabContainer.selectedIndex = tabNdx;
  1253.                 
  1254.                 // Add page to selected Tab UNLESS it already shows that page
  1255.                 openUILink(tNode.histNode.uri);
  1256.             }
  1257.         }
  1258.     },
  1259.  
  1260.     // ======================================================
  1261.     // Returns mTabContainer.selectedIndex for passed tabID
  1262.     // ONLY called from historyTree.openOrGotoHistoryPage()
  1263.     // ======================================================
  1264.     tabNdxFromTabID: function(tabID)
  1265.     {
  1266.         // Search for Tab/Browser that has passed tabID
  1267.         for (var browNdx = 0; browNdx < gBrowser.browsers.length; browNdx++)
  1268.         {
  1269.             if (tabID === gBrowser.mTabs[browNdx].linkedPanel)
  1270.                 return browNdx;
  1271.         }
  1272.  
  1273.         // Tab/Browser not found - So return -1
  1274.         return -1;
  1275.     },
  1276.  
  1277.     // ============================================================
  1278.     // Returns true if all Tabs content is loaded, or false if not
  1279.     // ============================================================
  1280.     allTabsLoaded: function() 
  1281.     {
  1282.         // Return true if FF error, so any setInterval() terminates
  1283.         // Perhaps caused by "Reload All Chrome" or other...?
  1284.         if (gBrowser === null)
  1285.             return true;
  1286.         
  1287.         // Return true if all Tabs have finished loading
  1288.         var brow;
  1289.         for (var i = 0; i < gBrowser.browsers.length; i++) 
  1290.         {
  1291.             brow = gBrowser.getBrowserAtIndex(i);
  1292.             if (brow.webProgress.isLoadingDocument) 
  1293.                 return false;
  1294.         }
  1295.         return true;
  1296.     },
  1297.  
  1298.     // ======================================================
  1299.     // Puts data needed by the extension's main window in 
  1300.     // Application storage - Called from "historyViewer.js" 
  1301.     // ======================================================
  1302.     putHistoryDataInAppStorage: function() 
  1303.     {
  1304.         var tabID;    // FF Tab identifier
  1305.         var openTabIDs = new Array();
  1306.  
  1307.         // --------------------------------------------------
  1308.         // Process all Tabs before opening extension window
  1309.         for (var i = 0; i < gBrowser.browsers.length; i++)
  1310.         {
  1311.             // Add to array list of currently open tabID's
  1312.             tabID = gBrowser.mTabs[i].linkedPanel;
  1313.             openTabIDs.push(tabID);
  1314.             
  1315.             // Update cNodeList[] sessHist entries for this Tab
  1316.             historyTree.updateSessionHistoryForTab(tabID);
  1317.             historyTree.synchSessHistChainForTab(tabID);
  1318.         }
  1319.  
  1320.         // ----------------------------------------------------------
  1321.         // Store 3 arrays in FF Application area (FF 3+ only)
  1322.         // This allow their retrieval in the extension main window
  1323.         historyTree.putWinParamsArrayInAppStorage();
  1324.         Application.storage.set("nodeArray", historyTree.cNodeList);
  1325.         Application.storage.set("tabIDarray", openTabIDs);
  1326.     },
  1327.  
  1328.     // ====================================================
  1329.     // Puts params needed by the extension in App storage
  1330.     // ====================================================
  1331.     putWinParamsArrayInAppStorage: function() 
  1332.     {
  1333.         // Put data in the params array (passed into extension window)
  1334.         var winParams = new Array();
  1335.         var tabID = gBrowser.tabContainer.selectedItem.linkedPanel;
  1336.         var sessHist = historyTree.sessHistFromTabID(tabID);
  1337.         var node = historyTree.nodeFromSessHistNdx(sessHist.index, tabID);
  1338.         
  1339.         winParams.push(tabID);      // tabID for visible Tab
  1340.         winParams.push(node);      // HistoryNode for visible page
  1341.  
  1342.         // Put the array in App storage
  1343.         Application.storage.set("paramArray", winParams);
  1344.     },
  1345.  
  1346.     // ========================================================
  1347.     // Returns a list of all open Tabs currently open in FF
  1348.     // *** ONLY called from "historyViewer.js" onFocus() event
  1349.     // ========================================================
  1350.     openTabIDsList: function() 
  1351.     {
  1352.         var openTabIDs = new Array();
  1353.         var tabID;
  1354.  
  1355.         // --------------------------------------------------
  1356.         // Process all Tabs before opening extension window
  1357.         for (var i = 0; i < gBrowser.browsers.length; i++)
  1358.         {
  1359.             // Add to array list of currently open tabID's
  1360.             tabID = gBrowser.mTabs[i].linkedPanel;
  1361.             openTabIDs.push(tabID);
  1362.         }
  1363.  
  1364.         // Return array of open TabIDs
  1365.         return openTabIDs;
  1366.     },
  1367.  
  1368.     // =============================================================
  1369.     // Returns true if any tab sessHist chain has been shortened
  1370.     // Detects history deletions via "Tools > Clear Recent History"
  1371.     // *** ONLY called from "historyViewer.js" onFocus()event
  1372.     // =============================================================
  1373.     recentFFhistoryCleared: function()
  1374.     {
  1375.         var sessHist;
  1376.         var tabID;
  1377.         var myLastNdx, lastFFndx;
  1378.  
  1379.         // Check all open tabs
  1380.         for (var browNdx = 0; browNdx < gBrowser.browsers.length; browNdx++)
  1381.         {
  1382.             // Get my last sessHistNdx
  1383.             tabID = gBrowser.mTabs[browNdx].linkedPanel;
  1384.             myLastNdx = historyTree.sessHistLengthForTab(tabID);
  1385.             
  1386.             // Get FF last sessHistNdx
  1387.             sessHist = gBrowser.getBrowserAtIndex(browNdx).sessionHistory;
  1388.             lastFFndx = sessHist.count - 1;
  1389.             
  1390.             // Check if this tabs chain has been shortened
  1391.             if (myLastNdx > lastFFndx)
  1392.                 return true;
  1393.         }
  1394.  
  1395.         // No tab sessHist chain has been shortened
  1396.         return false;
  1397.     },
  1398.  
  1399. };    
  1400. // End of extension overlay and HistoryTree.prototype